﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Net;
using System.IO;
using System.Diagnostics;

namespace War3GameBroadcaster
{
    public partial class FormSettings : Form
    {
        Socket broadcastSocket = new Socket( AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        Socket localBroadcastSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

        Socket listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        class AcceptData
        {
            public Socket socket;
            public byte[] buffer = new byte[1024];
        }

        int hostPort = 0;
        int proxyPort = 0;

        IPEndPoint RemoteEndPoint
        {
            get
            {
                return broadcastSocket.RemoteEndPoint as IPEndPoint;
            }
        }

        IPEndPoint ListenEndpoint
        {
            get
            {
                return listenSocket.LocalEndPoint as IPEndPoint;
            }
        }

        ~FormSettings()
        {
            notifyIcon1.Dispose();
        }
        byte[] broadcastcmd = ASCIIEncoding.ASCII.GetBytes("rcon_broadcast");

        public FormSettings()
        {
            InitializeComponent();
            Trace.Listeners.Add(new ListBoxTraceListener(lbLog));

            try
            {

                //Properties.Settings.Default.PropertyChanged += new PropertyChangedEventHandler(Default_PropertyChanged);
                listenSocket.NoDelay = true;
                listenSocket.Bind(new IPEndPoint(IPAddress.Any, 0));
                proxyPort = ((IPEndPoint)listenSocket.LocalEndPoint).Port;

                localBroadcastSocket.EnableBroadcast = true;

                AcceptData sockData = new AcceptData();
                sockData.socket = listenSocket;

                Trace.WriteLine("Listening for client connections on " + ListenEndpoint.Address + ":" + ListenEndpoint.Port);
                listenSocket.Listen(10);

                listenSocket.BeginAccept(new AsyncCallback(acceptCallback), sockData);
                button1_Click(this, EventArgs.Empty);
            }
            catch (Exception ex)
            {
                Trace.WriteLine("Startup error: " + ex.Message);
            }
        }

        void Default_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "Hostname":
                    break;
                case "Port":
                    break;
                case "RefreshInterval":
                    //Trace.WriteLine("Refresh interval set to " + Properties.Settings.Default.RefreshInterval + " ms");
                    break;
            }
        }

        class UdpData
        {
            public Socket socket;
            public byte[] buffer = new byte[1024];
        }

        class ClientData
        {
            public byte[] recvBuffer = new byte[4096];
            public byte[] sendBuffer = new byte[4096];
            public Socket clientSocket;
            public Socket serverSocket;
        }

        void sendBroadcastRequest()
        {
            //Trace.WriteLine("Requesting gamelist");
            try
            {
                broadcastSocket.Send(broadcastcmd, SocketFlags.None);
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.Message);
            }
        }

        void resolvCallback(IAsyncResult result)
        {
            try
            {
                IPHostEntry entry = Dns.EndGetHostEntry(result);
                Trace.WriteLine("Connecting to " + entry.AddressList[0]);
                broadcastSocket.BeginConnect(new IPEndPoint(entry.AddressList[0], (int)Properties.Settings.Default.Port), new AsyncCallback(udpConnectCallback), null);
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.Message);
            }
        }

        void udpConnectCallback(IAsyncResult result)
        {
            try
            {
                broadcastSocket.EndConnect(result);
                Trace.WriteLine("Connected to remote console");
                UdpData data = new UdpData();
                data.socket = broadcastSocket;
                //data.socket.EnableBroadcast = true;
                //data.socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);

                broadcastSocket.BeginReceive(data.buffer, 0, data.buffer.Length, SocketFlags.None, new AsyncCallback(udpRecvCallback), data);
                refreshTimer.Enabled = true;
                refreshTimer.Start();
            }
            catch (Exception ex)
            {
                Trace.WriteLine("Could not connect: " + ex.Message);
            }
        }

        void udpRecvCallback(IAsyncResult result)
        {
            UdpData data = result.AsyncState as UdpData;
            try
            {
                int count = data.socket.EndReceive(result);


                using (MemoryStream s = new MemoryStream(data.buffer, 0, count))
                {
                    s.Seek(-2, SeekOrigin.End);
                    BinaryReader r = new BinaryReader(s);
                    BinaryWriter w = new BinaryWriter(s);
                    hostPort = r.ReadUInt16();

                    s.Seek(-2, SeekOrigin.End);
                    w.Write((UInt16)proxyPort);
                }

                UdpData newData = new UdpData();
                newData.socket = data.socket;
                data.socket.BeginReceive(
                    newData.buffer, 0, newData.buffer.Length,
                    SocketFlags.None,
                    new AsyncCallback(udpRecvCallback),
                    newData);


                try
                {
                    IPEndPoint ep = new IPEndPoint(IPAddress.Broadcast, 6112);
                    if (Properties.Settings.Default.OnlyLocalBroadcast)
                    {
                        ep.Address = IPAddress.Loopback;
                    }
                    data.socket = localBroadcastSocket;
                    localBroadcastSocket.BeginSendTo(
                            data.buffer, 0, count,
                            SocketFlags.None,
                            ep,
                            new AsyncCallback(udpSendBroadcastCallback),
                            data);
                }
                catch (Exception ex)
                {
                    Trace.WriteLine("Relay error: " + ex.Message);
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine("UDP Receive error: " + ex.Message);
            }
        }

        void udpSendBroadcastCallback(IAsyncResult result)
        {
            try
            {
                UdpData data = result.AsyncState as UdpData;
                int count = data.socket.EndSendTo(result);
            }
            catch (Exception ex)
            {
                Trace.WriteLine("Relay callback error: " + ex.Message);
            }
        }

        void acceptCallback(IAsyncResult result)
        {
            AcceptData d = result.AsyncState as AcceptData;
            Socket client = d.socket.EndAccept(result);
            Trace.WriteLine("New client connected to proxy from " + client.RemoteEndPoint);

            ClientData data = new ClientData();
            data.clientSocket = client;

            data.serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);


            data.serverSocket.Connect(RemoteEndPoint.Address, hostPort);

            data.serverSocket.BeginReceive(data.sendBuffer, 0, data.sendBuffer.Length, SocketFlags.None, new AsyncCallback(serverRecvCallback), data);

            client.BeginReceive(data.recvBuffer, 0, data.recvBuffer.Length, SocketFlags.None, new AsyncCallback(clientRecvCallback), data);

            d.socket.BeginAccept(new AsyncCallback(acceptCallback), result.AsyncState);
        }

        void serverRecvCallback(IAsyncResult result)
        {
            ClientData data = result.AsyncState as ClientData;
            try
            {
                int count = data.serverSocket.EndReceive(result);
                data.clientSocket.Send(data.sendBuffer, 0, count, SocketFlags.None);

                data.serverSocket.BeginReceive(data.sendBuffer, 0, data.sendBuffer.Length, SocketFlags.None, new AsyncCallback(serverRecvCallback), data);
            }
            catch (Exception)
            {
                //if (data.clientSocket.Connected)
                    //data.clientSocket.Close();
                if (data.serverSocket.Connected)
                    data.serverSocket.Close();
            }
        }

        void clientRecvCallback(IAsyncResult result)
        {
            ClientData data = result.AsyncState as ClientData;
            try
            {
                int count = data.clientSocket.EndReceive(result);

                // forward data to server
                data.serverSocket.Send(data.recvBuffer, 0, count, SocketFlags.None);

                data.clientSocket.BeginReceive(data.recvBuffer, 0, data.recvBuffer.Length, SocketFlags.None, new AsyncCallback(clientRecvCallback), data);
            }
            catch (Exception ex)
            {
                Trace.WriteLine("Client disconnected: " + ex.Message);
                data.serverSocket.Close();
                if (data.clientSocket.Connected)
                    data.clientSocket.Close();
                //if (data.serverSocket.Connected)
                    //data.clientSocket.Close();
            }
        }

        private void trayIcon_DoubleClick(object sender, EventArgs e)
        {
            Visible = !Visible;
            if (Visible)
            {
                WindowState = FormWindowState.Normal;
                BringToFront();
                Activate();
            }
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (e.CloseReason == CloseReason.UserClosing)
            {
                e.Cancel = true;
                Hide();
            }
            Properties.Settings.Default.Save();
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void refreshTimer_Tick(object sender, EventArgs e)
        {
            sendBroadcastRequest();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //if (broadcastSocket != null && broadcastSocket.Connected)
                //broadcastSocket.Disconnect(true);
            
            Trace.WriteLine("Resolving " + Properties.Settings.Default.Hostname);
            Dns.BeginGetHostEntry(Properties.Settings.Default.Hostname, new AsyncCallback(resolvCallback), null);
        }

        private void trackBar1_MouseUp(object sender, MouseEventArgs e)
        {
            Trace.WriteLine("Refresh interval set to " + Properties.Settings.Default.RefreshInterval + " ms");
        }

        private void trackBar1_KeyUp(object sender, KeyEventArgs e)
        {
            Trace.WriteLine("Refresh interval set to " + Properties.Settings.Default.RefreshInterval + " ms");
        }
    }
}
